
模板参数有三种基本类型:

\begin{enumerate}
\item 
类型参数(最常见的)

\item 
非类型参数

\item 
双重模板参数
\end{enumerate}	

这些基本类型的模板参数都可以作为模板参数包的基础(参见12.2.4节)。

模板参数在模板声明的介绍性参数化子句中进行声明。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}C++14以来的例外是泛型Lambda的隐式模板类型参数，参见15.10.6节。
\end{tcolorbox}

这样的声明不一定需要命名:

\begin{lstlisting}[style=styleCXX]
template<typename, int>
class X; // X<> is parameterized by a type and an integer
\end{lstlisting}

若参数在模板中引用，则需要参数名。模板参数名称可以在后续的参数声明中引用(但不能在前面引用):

\begin{lstlisting}[style=styleCXX]
template<typename T, // the first parameter is used
		T Root, // in the declaration of the second one and
		template<T> class Buf> // in the declaration of the third one
class Structure;
\end{lstlisting}

\subsubsubsection{12.2.1\hspace{0.2cm}类型参数}

类型参数是通过关键字typename或关键字class引入的:两者等价。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}关键字class并不意味着替换参数应该是类类型，还可以是一个可访问的类型。
\end{tcolorbox}

关键字后面必须跟着一个简单的标识符，该标识符后面必须跟着一个逗号，表示下一个参数声明的开始，一个结束尖括号(>)表示参数化子句的结束，或者一个等号(=)表示默认模板参数的开始。

模板声明中，类型形参的作用类似于类型别名(参见2.8节)。当T是模板参数时，不能使用参数T的名称:

\begin{lstlisting}[style=styleCXX]
template<typename Allocator>
class List {
	class Allocator* allocptr; // ERROR: use “Allocator* allocptr”
	friend class Allocator; // ERROR: use “friend Allocator”
	...
};
\end{lstlisting}

\subsubsubsection{12.2.2\hspace{0.2cm}非类型参数}

非类型模板参数表示可在编译或链接时确定的常量。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}模板参数也不表示类型，但不同于非类型参数。这也是个历史问题:双重模板参数在类型参数和非类型参数之后添加。
\end{tcolorbox}

这样的参数类型(换句话说，代表的类型)必须是下列之一:

\begin{itemize}
\item 
整数类型或枚举类型

\item 
指针类型

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}撰写本文时，只允许“指向对象的指针”和“指向函数的指针”类型，这就排除了像void*这样的类型。然而，所有的编译器似乎也能接受void*。
\end{tcolorbox}

\item 
成员指针类型

\item
左值引用类型(对对象的引用和对函数的引用都可以)

\item 
std::nullptr\_t

\item 
包含auto或decltype(auto)的类型(仅C++17中可用，参见15.10.1节)
\end{itemize}

所有其他类型目前都排除在外(尽管浮点类型可能会在将来添加，参见17.2节)。

非类型模板参数的声明，某些情况下也使用关键字typename开头:

\begin{lstlisting}[style=styleCXX]
template<typename T, // a type parameter
		typename T::Allocator* Allocator> // a nontype parameter
class List;
\end{lstlisting}

或者使用关键字class:

\begin{lstlisting}[style=styleCXX]
template<class X*> // a nontype parameter of pointer type
class Y;
\end{lstlisting}

这两种情况很容易区分，因为第一个后面跟着一个简单的标识符，然后是一段标记('='表示默认实参，','表示后面跟着另一个模板参数，或者用>结束模板参数列表)。5.1节和的13.3.2节解释了，在第一个非类型参数中使用typename关键字的必要性。

函数和数组类型可以指定，但会隐式衰变为指针类型:

\begin{lstlisting}[style=styleCXX]
template<int buf[5]> class Lexer; // buf is really an int*
template<int* buf> class Lexer; // OK: this is a redeclaration
template<int fun()> struct FuncWrap; // fun really has pointer to
								     // function type
template<int (*)()> struct FuncWrap; // OK: this is a redeclaration
\end{lstlisting}

非类型模板参数的声明很像变量，但它们不能有静态、可变等非类型修饰符。它们可以有const和volatile限定符，但若这样的限定符出现在参数类型的最外层，就会忽略:

\begin{lstlisting}[style=styleCXX]
template<int const length> class Buffer; // const is useless here
template<int length> class Buffer; // same as previous declaration
\end{lstlisting}

最后，非引用非类型参数在表达式中使用时，始终是prvalue。 

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}参见附录B中关于值类别的讨论，比如右值和左值。
\end{tcolorbox}

地址不能取走，也不能赋予。另一方面，左值引用类型的非类型参数可用于表示左值:

\begin{lstlisting}[style=styleCXX]
template<int& Counter>
struct LocalIncrement {
	LocalIncrement() { Counter = Counter + 1; } // OK: reference to an integer
	~LocalIncrement() { Counter = Counter - 1; }
};
\end{lstlisting}

并且，不允许右值引用。

\subsubsubsection{12.2.3\hspace{0.2cm}双重模板参数}

双重参数是类模板或别名模板的占位符。声明很像类模板，但关键字struct和union不能使用:

\begin{lstlisting}[style=styleCXX]
template<template<typename X> class C> // OK
void f(C<int>* p);

template<template<typename X> struct C> // ERROR: struct not valid here
void f(C<int>* p);

template<template<typename X> union C> // ERROR: union not valid here
void f(C<int>* p);
\end{lstlisting}

C++17允许使用typename，而非class。因为双重模板参数不仅可以使用类模板替换，还可以使用别名模板(别名模板实例化为任意类型)替换。C++17中，上面的例子也可以写成

\begin{lstlisting}[style=styleCXX]
template<template<typename X> typename C> // OK since C++17
void f(C<int>* p);
\end{lstlisting}

双重模板参数声明的作用域内，使用方式和其他类或别名模板一样。

模板参数可以有默认值。这些默认值适用于没有指定相应参数的情况:

\begin{lstlisting}[style=styleCXX]
template<template<typename T,
				  typename A = MyAllocator> class Container>
class Adaptation {
	Container<int> storage; // implicitly equivalent to Container<int,MyAllocator>
	...
};
\end{lstlisting}

T和A是模板参数Container的模板参数名，只能在该模板参数的其他参数声明中使用:

\begin{lstlisting}[style=styleCXX]
template<template<typename T, T*> class Buf> // OK
class Lexer {
	static T* storage; // ERROR: a template template parameter cannot be used here
	...
};
\end{lstlisting}

然而，通常模板参数名在声明其他模板参数时是不需要的，因此可以不命名。例如，之前的Adaptation模板的声明可以这样写:

\begin{lstlisting}[style=styleCXX]
template<template<typename,
				  typename = MyAllocator> class Container>
class Adaptation {
	Container<int> storage; // implicitly equivalent to Container<int,MyAllocator>
	...
};
\end{lstlisting}

\subsubsubsection{12.2.4\hspace{0.2cm}模板参数包}

C++11后，任何类型的模板参数都可以通过在模板参数名之前引入省略号(…)转换为模板参数包。若板参数未命名，需要对模板参数进行命名:

\begin{lstlisting}[style=styleCXX]
template<typename... Types> // declares a template parameter pack named Types
class Tuple;
\end{lstlisting}

模板参数包的行为与其底层模板参数相似，但有一个关键区别:普通模板参数只能匹配一个模板参数，而模板参数包可以匹配任意数量的模板参数。所以，上面声明的Tuple类模板接受任意数量(可能是不同的)的类型作为模板参数:

\begin{lstlisting}[style=styleCXX]
using IntTuple = Tuple<int>; // OK: one template argument
using IntCharTuple = Tuple<int, char>; // OK: two template arguments
using IntTriple = Tuple<int, int, int>; // OK: three template arguments
using EmptyTuple = Tuple<>; // OK: zero template arguments
\end{lstlisting}

类似地，非类型参数和模板参数包可以接受任意数量的非类型参数和模板参数:

\begin{lstlisting}[style=styleCXX]
template<typename T, unsigned... Dimensions>
class MultiArray; // OK: declares a nontype template parameter pack

using TransformMatrix = MultiArray<double, 3, 3>; // OK: 3x3 matrix

template<typename T, template<typename,typename>... Containers>
void testContainers(); // OK: declares a template template parameter pack
\end{lstlisting}

MultiArray要求所有非类型模板参数具有无符号类型。C++17引入了推导非类型模板参数的可能，在一定程度上可以绕过这个限制——请参阅15.10.1节了解详细信息。

主模板、变量模板和别名模板最多可以有一个模板参数包。若模板参数包是最后一个模板参数，则函数模板有一个较弱的限制:允许多个模板参数包，只要模板参数包后面的每个模板参数要么有一个默认值(参见下一节)，要么可以推导(参见第15章):

\begin{lstlisting}[style=styleCXX]
template<typename... Types, typename Last>
class LastType; // ERROR: template parameter pack is not the last template parameter

template<typename... TestTypes, typename T>
void runTests(T value); // OK: template parameter pack is followed
// by a deducible template parameter

template<unsigned...> struct Tensor;
template<unsigned... Dims1, unsigned... Dims2>
auto compose(Tensor<Dims1...>, Tensor<Dims2...>);
// OK: the tensor dimensions can be deduced
\end{lstlisting}

最后一个例子使用了返回类型推导——C++14的特性。请参见第15.10.1节。

类和变量模板的偏特化声明(参见第16章)可以有多个参数包。与主模板对应的参数包不同，这是因为偏特化是通过推导选择的，该推导过程与函数模板的推导过程相同。

\begin{lstlisting}[style=styleCXX]
template<typename...> Typelist;
template<typename X, typename Y> struct Zip;
template<typename... Xs, typename... Ys>
	struct Zip<Typelist<Xs...>, Typelist<Ys...>>;
	// OK: partial specialization uses deduction to determine
	// the Xs and Ys substitutions
\end{lstlisting}

类型参数包不能在自己的参数子句中展开。例如:

\begin{lstlisting}[style=styleCXX]
template<typename... Ts, Ts... vals> struct StaticValues {};
// ERROR: Ts cannot be expanded in its own parameter list
\end{lstlisting}

然而，嵌套模板可以创建类似且有效的代码:

\begin{lstlisting}[style=styleCXX]
template<typename... Ts> struct ArgList {
	template<Ts... vals> struct Vals {};
};
ArgList<int, char, char>::Vals<3, ’x’, ’y’> tada;
\end{lstlisting}

因为能接受可变数量的模板参数，所以包含模板参数包的模板称为可变参数模板。第4章和第12.4节在描述了可变参数模板的使用。

\subsubsubsection{12.2.5\hspace{0.2cm}默认模板参数}

非模板参数包中的模板参数都可以配备一个默认参数，必须在类型上与相应的参数匹配(例如，类型参数不能有非类型的默认参数)。默认参数不能依赖于自己的参数，因为参数名直到默认参数之后才在作用域中。但是，其可能取决于之前的参数:

\begin{lstlisting}[style=styleCXX]
template<typename T, typename Allocator = allocator<T>>
class List;
\end{lstlisting}

只有在后续参数也提供了默认参数的情况下，类模板、变量模板或别名模板的模板参数才可以有默认模板参数(对于默认函数调用参数也存在类似的约束)。随后的默认值通常在同一个模板声明中提供，但可以在该模板的声明中进行声明:

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2, typename T3,
typename T4 = char, typename T5 = char>
class Quintuple; // OK

template<typename T1, typename T2, typename T3 = char,
typename T4, typename T5>
class Quintuple; // OK: T4 and T5 already have defaults

template<typename T1 = char, typename T2, typename T3,
typename T4, typename T5>
class Quintuple; // ERROR: T1 cannot have a default argument
// because T2 doesn’t have a default
\end{lstlisting}

函数模板的参数，默认模板参数不需要后续模板参数有默认值:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}后续模板参数的模板参数，可以通过模板参数推导来确定，参见第15章。
\end{tcolorbox}

\begin{lstlisting}[style=styleCXX]
template<typename R = void, typename T>
R* addressof(T& value); // OK: if not explicitly specified, R will be void
\end{lstlisting}

默认模板参数不能重复:

\begin{lstlisting}[style=styleCXX]
template<typename T = void>
class Value;

template<typename T = void>
class Value; // ERROR: repeated default argument
\end{lstlisting}

许多上下文不允许默认模板参数:

\begin{itemize}
\item 
偏特化:

\begin{lstlisting}[style=styleCXX]
template<typename T>
class C;
...
template<typename T = int>
class C<T*>; // ERROR
\end{lstlisting}

\item 
参数包:

\begin{lstlisting}[style=styleCXX]
template<typename... Ts = int> struct X; // ERROR
\end{lstlisting}

\item 
类模板成员类外定义：

\begin{lstlisting}[style=styleCXX]
template<typename T> struct X
{
	T f();
};

template<typename T = int> T X<T>::f() { // ERROR
	...
}
\end{lstlisting}

\item 
友元类模板声明:

\begin{lstlisting}[style=styleCXX]
struct S {
	template<typename = void> friend struct F;
};
\end{lstlisting}

\item 
友元函数模板声明，除非是一个定义，并且在编译单元的其他地方没有声明:

\begin{lstlisting}[style=styleCXX]
struct S {
	template<typename = void> friend void f(); // ERROR: not a definition
	template<typename = void> friend void g() { // OK so far
	}
};
template<typename> void g(); // ERROR: g() was given a default template argument
// when defined; no other declaration may exist here
\end{lstlisting}

\end{itemize}















